﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace B303Langf.Scanner
{
   public class Scanner : IScanner
   {
      private readonly BinaryReader m_Reader;
      private StringBuilder m_CurrentSpelling;
      private char? m_CurrentChar;

      public Scanner( Stream stream )
      {
         m_Reader = new BinaryReader( stream );
         m_CurrentSpelling = new StringBuilder();
         m_CurrentChar = m_Reader.ReadChar();
      }

      private void TakeIt()
      {
         m_CurrentSpelling.Append( m_CurrentChar );

         if( m_Reader.PeekChar() != -1 )
         {
            m_CurrentChar = m_Reader.ReadChar();
         }
         else
         {
            m_CurrentChar = null;
         }
      }

      private void ThrowIt()
      {
         if( m_Reader.PeekChar() != -1 )
         {
            m_CurrentChar = m_Reader.ReadChar();
         }
         else
         {
            m_CurrentChar = null;
         }
      }

      private bool IsLetter()
      {
         return m_CurrentChar >= 'a' && m_CurrentChar <= 'z' || m_CurrentChar >= 'A' && m_CurrentChar <= 'Z' || m_CurrentChar == '!' || m_CurrentChar == '_';
      }

      private bool IsDigit()
      {
         return m_CurrentChar >= '0' && m_CurrentChar <= '9';
      }

      private bool IsSeparator()
      {
         return m_CurrentChar == ' ' || m_CurrentChar == '\n' || m_CurrentChar == '\r' || m_CurrentChar == '\t';
      }

      private bool IsCharWrapper()
      {
         return m_CurrentChar == '\'';
      }

      private bool IsStringWrapper()
      {
         return m_CurrentChar == '\"';
      }

      private void ScanSeparator()
      {
         TakeIt();
      }

      private TokenKind ScanToken()
      {
         if( IsLetter() ) // also consumes operators
         {
            TakeIt();
            while( IsLetter() || IsDigit() )
            {
               TakeIt();
            }
            return TokenKind.Identifier;
         }
         else if( IsDigit() )
         {
            TakeIt();
            while( IsDigit() )
            {
               TakeIt();
            }
            return TokenKind.IntegerLiteral;
         }
         switch( m_CurrentChar )
         {
            case '[':
               TakeIt();
               return TokenKind.BlockBegin;
            case ']':
               TakeIt();
               return TokenKind.BlockEnd;
            case '?':
               TakeIt();
               return TokenKind.Terminator;
            case ',':
               TakeIt();
               return TokenKind.Comma;
            case '<':
               TakeIt();
               return TokenKind.ArrayIndicatorBegin;
            case '>':
               TakeIt();
               return TokenKind.ArrayIndicatorEnd;
            case '(':
               TakeIt();
               return TokenKind.ParanthesisBegin;
            case ')':
               TakeIt();
               return TokenKind.ParanthesisEnd;
            case '"':
               ThrowIt();
               while (IsDigit() || IsLetter() || IsSeparator() )
               {
                  TakeIt();
               }
               if( !IsStringWrapper() )
               {
                  throw new UnrecognizedCharacterException( "A symbol array literal must end with a '\"'" );
               }
               ThrowIt();
               return TokenKind.CharArrayLiteral;
            case '\'':
               ThrowIt();
               if( IsDigit() || IsLetter() || IsSeparator() )
               {
                  TakeIt();
               }
               else
               {
                  throw new UnrecognizedCharacterException( "Invalid char literal" );
               }
               if( !IsCharWrapper() )
               {
                  throw new UnrecognizedCharacterException( "A symbol literal must end with a '\''" );
               }
               ThrowIt();
               return TokenKind.CharLiteral;
            case null:
               return TokenKind.EndOfTokens;
         }
         throw new UnrecognizedCharacterException( "Unrecognized character: " + m_CurrentChar );
      }

      public Token Scan()
      {
         // start by scanning separator
         while( IsSeparator() )
         {
            ScanSeparator();
         }

         m_CurrentSpelling = new StringBuilder();
         var kind = ScanToken();
         return new Token( kind, m_CurrentSpelling.ToString() );
      }

      public void Dispose()
      {
         m_Reader.Dispose();
      }
   }
}
